﻿/* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 * '' Product        : Consilium
 * ''
 * '' Description    : Video Game
 * ''
 * '' Author         : Group #4
 * ''
 * '' Platforms      : This version is supported for Microsoft Windows
 * ''
 * ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 
 */
using System;
using System.Xml;

using GameCommon;

namespace GameModel
{
    // TODO: Add turn attribute to specification.
    // TODO: Add endTurn method to specification.
    /// <summary>
    ///   The <c>Model</c> handles the conceptual model of a Consilium
    ///   match.
    /// </summary>
    /// <remarks>
    ///   <para>
    ///     Game state can be read via the <c>Model.map</c>,
    ///     <c>Model.teams</c>, <c>Model.turn</c>. Outside code should
    ///     call public methods to perform discrete game operations.
    ///     They will, in turn, raise events describing caused changes
    ///     of state. If a move is invalid, no changes or events occur.
    ///   </para>
    ///   <para>
    ///     Note: If <c>Model</c> is not compiled as an assembly,
    ///     certain direct modifications of game state are possible.
    ///     These are strongly discouraged.
    ///   </para>
    /// </remarks>
    class Model
    {
        /// <summary>The current game map.</summary>
        public readonly Map map;

        /// <summary>A list of teams in the order of turns.</summary>
        public readonly Team[] teams;

        /// <summary>The team currently in control.</summary>
        public Team turn { get { return teams[myTurn]; } }
        private int myTurn;

        // EVENTS: Still unused events left commented
        public event GameWon GameWonEvent;
        public event NewTurn NewTurnEvent;
        public event UnitKilled UnitKilledEvent;
        //public event UnitCreated UnitCreatedEvent;
        public event UnitMoves UnitMovesEvent;
        public event UnitAttacks UnitAttacksEvent;
        //public event UnitRetaliates UnitRetaliatesEvent;
        //public event ZoneCaptured ZoneCapturedEvent;
        //public event MineAcquired MineAcquiredEvent;
        //public event MineDepleted MineDepletedEvent;
        //public event GoldChanges GoldChangesEvent;

        /// <summary>Instantiate a model.</summary>
        /// <param name="mapFilename">A *.map file.</param>
        /// <param name="scenarioFilename">A *.scenario file.</param>
        /// <see cref="mapformat"/>
        public Model(string mapFilename, string scenarioFilename)
        {
            XmlDocument scenarioDoc = new XmlDocument();
            try { scenarioDoc.Load(scenarioFilename); }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Environment.Exit(1);
            }

            map = new Map(mapFilename, scenarioDoc);
            teams = new Team[map.numTeams];
            myTurn = 0;
        }

        /// <summary>
        ///   Cause any unit standing on (<c>fromX</c>, <c>fromY</c>) to
        ///   move to (<c>toX</c>, <c>toY</c>). Will raise events to
        ///   broadcast results.
        /// </summary>
        public void moveUnit(int fromX, int fromY, int toX, int toY)
        {
            // Check for bad input tiles.
            if (0 > fromX || map.width <= fromX ||
                0 > fromY || map.height <= fromY)
                return;

            // Fail if the start tile does not contain a unit, or if the
            // destination tile already does.
            Tile from = map.tiles[fromX, fromY],
                 to = map.tiles[toX, toY];
            if (from.unit == null || to.unit != null)
                return;

            // Fail if current player does not have the right to move the unit.
            if (from.unit.team != turn)
                return;

            // Perform move and post alert.
            to.unit = from.unit;
            to.unit.x = to.x;
            to.unit.y = to.y;
            from.unit = null;
            UnitMovesEvent(this, fromX, fromY, toX, toY);
        }

        /// <summary>
        ///   Cause any unit standing on (<c>fromX</c>, <c>fromY</c>) to
        ///   attack any unit on (<c>toX</c>, <c>toY</c>). Will raise
        ///   events to broadcast results.
        /// </summary>
        public void attackUnit(int fromX, int fromY, int toX, int toY)
        {
            // Check for bad input tiles.
            if (0 > fromX || map.width <= fromX ||
                0 > fromY || map.height <= fromY)
                return;

            // Fail if the start tile does not contain a friendly unit, or the
            // other tile an enemy unity
            Tile from = map.tiles[fromX, fromY],
                 to = map.tiles[toX, toY];
            if (from.unit == null || to.unit == null ||
                from.unit.team != turn || to.unit.team == turn)
                return;

            // TODO: range
            // TODO: attack poitns
            int damage = 1;
            to.unit.hitpoints -= damage;

            UnitAttacksEvent(this, fromX, fromY, toX, toY, damage);
            if (to.unit.hitpoints <= 0)
            {
                Team unitTeam = to.unit.team;
                unitTeam.units.Remove(to.unit);
                to.unit = null;
                UnitKilledEvent(this, toX, toY);

                // TODO: more realistic victory condition
                if (unitTeam.units.Count == 0)
                    GameWonEvent(this, unitTeam.color);
            }

            // TODO retaliation
        }

        /// <summary>
        ///   Attempts to purchase a unit of type <c>unit</c> at base
        ///   location (<c>baseX</c>, <c>baseY</c>). Will raise events
        ///   to broadcast results.
        /// </summary>
        public void createUnit(int baseX, int baseY, UnitType unit)
        {
        }

        /// <summary>
        ///   Attempts to increment the game state a turn. This will
        ///   only succeed if <c>turn.isHuman</c>.
        /// </summary>
        public void endTurn()
        {
            myTurn++;
            if (myTurn >= teams.Length)
                myTurn = 0;
            NewTurnEvent(this, turn.color);
        }
    }
}
